package structure;

import gui.control.Pic;

/**
 * Bildet die Register des PIC16 nach. Enthaelt die Speicherstellen von 0x00 bis 0xFF.
 * Kann nur einmal instanziiert werden. Singleton Pattern.
 * Bietet zudem Methoden zum Auslesen und Setzen von Werten im Register.
 * @author Dorothee Kern und Patrick Lehmann
 *
 */
public class Register {

    private static Register instance = null;
    private int programCounter = 0;
    @SuppressWarnings("unused")
    private int programCounterHigh = 0;
    private boolean isBank1 = false;
    private Byte[] register = new Byte[0x100];
    private boolean TMR0preWritten = false;
    private Byte latchA = new Byte();
    private Byte latchB = new Byte();

    /**
     * Erstellt ein neues Register und initialisiert das Array mit Standartwerten
     */
    private Register() {
        //Standardwerte setzen
        reset();
    }

    /**
     * Liefert das Register zurueck. Kann nur einmal instanziert werden.
     * @return Globales Register
     */
    public static Register getInstance() {
        if (instance == null) {
            instance = new Register();
        }
        return instance;
    }

    //Getter und Setter
    public Byte getRegisterByte(int address) {
        //Fall indirect
        if (address == 0) {
            //@mike
            address = register[4].getByteAsInt();
            //address=4;
        }
        if (isBank1 == true) {
            return register[funcBank1(address)];
        }

        return register[address];

    }

    public int getRegisterByteAsInt(int address) {
        //Fall indirect
        if (address == 0) {
            //@mike
            address = register[4].getByteAsInt();
            //address=4;
        }
        if (isBank1 == true) {
            return register[funcBank1(address)].getByteAsInt();
        }

        return register[address].getByteAsInt();
    }

    public void setRegisterByte(int address, Byte value) {
        //Fall indirect
        if (address == 0) {
            address = register[4].getByteAsInt();
        }

        checkBank(address);
        //prüft ob Bank 1 oder Bank 0 aktiv ist
        if (isBank1 == false) {
            // Falls der TMR0 vorbelegt wird, das Attribut TMR0preWritten
            // auf true setzen
            if (address == 1) {
                TMR0preWritten = true;
            }
            //nur wenn nicht die Adressen 5 oder 6 aufgerufen wurden, diese setzen
            if (!(address == 5 || address == 6)) {
                register[address].setByte(value.getByteAsArray());
                //Schreibt den Wert in die gespiegelte Adresse
                register[checkMirror(address)].setByte(value.getByteAsArray());
            }
            //latch aufrufen wenn Adresse 5 oder 6 aufgerufen wurde
            latch(address, value);
        } else {
            register[funcBank1(address)].setByte(value.getByteAsArray());
            //Wenn die Tris A und B Adressen gesetzt wurden, latchWrite() aufrufen
            if ((address == 0x05) || (address == 0x06) || (address == 0x85) || (address == 0x86)) {
                latchWrite();
            }
        }
    }

    public void setRegisterByteAsInt(int address, int value) {
        //Fall indirect
        if (address == 0) {
            address = register[4].getByteAsInt();
        }

        checkBank(address);
        //prüft ob Bank 1 oder Bank 0 aktiv ist
        if (isBank1 == false) {
            // Falls der TMR0 vorbelegt wird, das Attribut TMR0preWritten
            // auf true setzen
            if (address == 1) {
                TMR0preWritten = true;
            }
            //nur wenn nicht die Adressen 5 oder 6 aufgerufen wurden, diese setzen
            if (!(address == 5 || address == 6)) {
                register[address].setByte(value);
                //Schreibt den Wert in die gespiegelte Adresse
                register[checkMirror(address)].setByte(value);
            }
            //latch aufrufen wenn Adresse 5 oder 6 aufgerufen wurde
            latchInt(address, value);
        } else {
            register[funcBank1(address)].setByte(value);
            //Wenn die Tris A und B Adressen gesetzt wurden, latchWrite() aufrufen
            if ((address == 0x05) || (address == 0x06) || (address == 0x85) || (address == 0x86)) {
                latchWrite();
            }
        }
    }

    public boolean getRegisterBit(int address, int pos) {
        //Fall indirect
        if (address == 0) {
            //@mike
            address = register[4].getByteAsInt();
            //address=4;
        }
        if (isBank1 == true) {
            return register[funcBank1(address)].getBit(pos);
        }

        return register[address].getBit(pos);
    }

    public void setRegisterBit(int address, int pos, boolean value) {
        //Fall indirect
        if (address == 0) {
            address = register[4].getByteAsInt();
        }
        checkBank(address);
        //prüft ob Bank 1 oder Bank 0 aktiv ist
        if (isBank1 == false) {
            // Falls der TMR0 vorbelegt wird, das Attribut TMR0preWritten
            // auf true setzen
            if (address == 1) {
                TMR0preWritten = true;
            }
            //nur wenn nicht die Adressen 5 oder 6 aufgerufen wurden, diese setzen
            if (!(address == 5 || address == 6)) {
                register[address].setBit(pos, value);
                //Schreibt den Wert in die gespiegelte Adresse
                register[checkMirror(address)].setBit(pos, value);
            }
            //latch aufrufen wenn Adresse 5 oder 6 aufgerufen wurde
            latchBit(address, pos, value);
        } else {
            register[funcBank1(address)].setBit(pos, value);
            //Wenn die Tris A und B Adressen gesetzt wurden, latchWrite() aufrufen
            if ((address == 0x05) || (address == 0x06) || (address == 0x85) || (address == 0x86)) {
                latchWrite();
            }
        }
    }

    /**
     * Gibt Byte als Integer für die Darstellung in der GUI zurück.
     * @param address Adresse im Register
     * @return Wert des Registers an der Adresse
     */
    public int getRegisterByteForGUI(int address) {
        return register[address].getByteAsInt();
    }

    /**
     * Gib ein Bit Darstellung in der GUI zurück.
     * Wird bei den Ports verwendet.
     * @param address Speicheradresse
     * @return Wert des Registers an der Adresse
     */
    public boolean getRegisterBitForGUI(int address, int pos) {
        return register[address].getBit(pos);
    }

    /**
     * Setzt ein Bit im Register. Wird bei den Ports auf der GUI verwendet.
     * @param address  Speicheradresse
     * @param pos Position des Bits
     * @param value Zu setzender Wert
     */
    public void setRegisterBitForGUI(int address, int pos, boolean value) {
        register[address].setBit(pos, value);
    }

    /**
     * Setzt ein Byte im Register ohne Bänke und andere Schalter zu beachten.
     * Wird benötigt für direkten Zugriff.
     * @param address Speicheradresse
     * @param value Zu setzender Wert
     */
    public void setRegisterByteAsIntForGUI(int address, int value) {
        register[address].setByte(value);
    }

    //Flag Methoden
    //Carry
    public void setCarry() {
        setRegisterBit(0x03, 0, true);
        setRegisterBit(0x83, 0, true);
    }

    public void clearCarry() {
        setRegisterBit(0x03, 0, false);
        setRegisterBit(0x83, 0, false);
    }

    public boolean getCarry() {
        return getRegisterBit(0x03, 0);
    }
    //DigitCarry

    public void setDigitCarry() {
        setRegisterBit(0x03, 1, true);
        setRegisterBit(0x83, 1, true);
    }

    public void clearDigitCarry() {
        setRegisterBit(0x03, 1, false);
        setRegisterBit(0x83, 1, false);
    }

    public boolean getDigitCarry() {
        return getRegisterBit(0x03, 1);

    }
    //Zero

    public void setZero() {
        setRegisterBit(0x03, 2, true);
        setRegisterBit(0x83, 2, true);
    }

    public void clearZero() {
        setRegisterBit(0x03, 2, false);
        setRegisterBit(0x83, 2, false);
    }

    public boolean getZero() {
        return getRegisterBit(0x03, 2);
    }

    //ProgramCounter Methoden
    /**
     * Erhoeht den ProgramCounter um den Wert 1.
     */
    public void incProgramCounter() {
        //Adresse des PC 0x02 wird ausgelesen, um 1 erhoeht und wieder gespeichert.
        int pc = ++programCounter;
        //Low Byte des PC in PCL Register speichern
        setRegisterByteAsInt(0x02, pc & 0xFF);
        //Low Byte des PC auch im gespiegelten PCL Register speichern
        setRegisterByteAsInt(0x82, pc & 0xFF);
        //

    }

    /**
     * 13 Bit PC auslesen.
     * @return
     */
    public int getProgramCounter() {
        return programCounter;
    }

    /**
     * 13 Bit PC setzen. Ebenso setzen von PCL und PCH.
     * @param value Neuer Wert fuer ProgramCounter
     */
    public void setProgramCounter(int value) {
        programCounter = value;

        //PCL mit 8 Bit maskierten Wert setzen
        setRegisterByteAsInt(0x02, 0xFF & value);
        //PCL mit 8 Bit maskierten Wert setzen in Spiegeladresse
        setRegisterByteAsInt(0x82, 0xFF & value);
        //PCH setzen
        programCounterHigh = (value & 0x1F00) >>> 8;
    }

    // Watchdog Methoden
    /**
     * Methode ueberprueft ob das Limit des Watchdogtimers erreicht ist.
     * Bei erreichen des WDT-Limit wird der WDT-Reset ausgeloest.
     */
    public void checkWDT() {
        int limit;
        int prescalor;
        //prüfen ob Prescalor dem WDT gehört
        if (getRegisterBit(0x81, 3) == true) {
            //Prescalor wird ausgelesen
            prescalor = getRegisterByteAsInt(0x81) & 0x7;
        } else {
            // wenn der Prescalor nicht dem WDT gehört iwird ihm der Wert 0 zugewiesen
            prescalor = 0;
        }
        //das limit wird zugewiesen; 18000 ist der Standardwert,wird mit Prescalor multipliziert;
        limit = (int) Math.pow(2, prescalor) * 18000;
        if (Pic.time == limit) {
            resetWDT();
        }
    }

    //Reset
    /**
     * Setzt das Register auf Standardwerte nach Power-On-Reset.
     */
    public void reset() {
        //PC auf 0 setzen
        programCounter = 0;
        programCounterHigh = 0;
        //Array mit 0 initialisieren
        for (int i = 0; i < register.length; i++) {
            register[i] = new Byte();
        }
        //Statusreg. 03h
        setRegisterByteAsInt(0x03, 0x18);
        //Optionreg. 81h
        setRegisterByteAsInt(0x81, 0xFF);
        //TRISA 85h
        setRegisterByteAsInt(0x85, 0x1F);
        //TRISB 86h
        setRegisterByteAsInt(0x86, 0xFF);
        //Latch A und B auf 0
        latchA.setByte(0);
        latchB.setByte(0);
    }

    /**
     * Setzt das Register auf Standardwerte nach Watch-Dog-Timer Reset.
     */
    public void resetWDT() {
        //PCL auf 0 setzen
        setProgramCounter(0);
        //Statusregister setzen Die oberen 3 Bits auf 0
        setRegisterBit(0x03, 7, false);
        setRegisterBit(0x03, 6, false);
        setRegisterBit(0x03, 5, false);
        setRegisterBit(0x03, 4, false);
        //PCL auf 0 setzen
        setRegisterByteAsInt(0x0A, 0);
        //Intcon alle außer LSB auf 0 setzten
        boolean temp = getRegisterBit(0x0B, 0);
        if (temp == false) {
            setRegisterByteAsInt(0x0B, 0);
        } else {
            setRegisterByteAsInt(0x0B, 1);
        }
        //Option Register mit FF belegen
        setRegisterByteAsInt(0x81, 0xFF);
        //PCL auf 0 setzen (Bank1)
        setRegisterByteAsInt(0x82, 0);
        //Status register setzten (Bank1)
        setRegisterBit(0x83, 7, false);
        setRegisterBit(0x83, 6, false);
        setRegisterBit(0x83, 5, false);
        //Tris A mit 1F belegen
        setRegisterByteAsInt(0x85, 0x1F);
        //Tris B mit FF belegen
        setRegisterByteAsInt(0x86, 0xFF);
        //EECON setzen
        boolean temp2 = getRegisterBit(0x88, 0);
        if (temp2 == false) {
            setRegisterByteAsInt(0x0B, 0);
        } else {
            setRegisterByteAsInt(0x0B, 8);
        }
        //Die Laufzeit zurücksetzen auf 0
        Pic.time = 0;

    }

    //Adressspiegelung
    /**
     * Uerberprueft ob eine gespiegelte Adresse uebergeben wurde.
     * Gibt die Spiegeladresse zurueck.
     * @param address Adresse die uebergeben wurde
     * @return Spiegeladresse
     */
    public int checkMirror(int address) {
        //prueft ob gespieglete Adresse uebergeben wurde
        if ((address == 0x00) | ((address >= 0x02) & (address <= 0x04)) | ((address >= 0x0A) & (address <= 0x2F))) {
            return address + 0x80;
        }
        if ((address == 0x80) | ((address >= 0x82) & (address <= 0x84)) | ((address >= 0x8A) & (address <= 0xAF))) {
            return address - 0x80;
        } else {
            return address;
        }
    }

    //Bänke umschalten
    /**
     * Ueberprueft auf welcher Bank gearbeitet werden soll, und schaltet auf
     * die betreffende Bank um.
     * @param address uebergebene Adresse
     * @return Adresse auf der gearbeitet werden soll
     */
    public void checkBank(int address) {
        if (getRegisterBit(0x03, 5) == true) {
            isBank1 = true;
        } else {
            isBank1 = false;
        }
    }

    public int funcBank1(int address) {
        if ((address == 0x01) | (address == 0x05) | (address == 0x06) | (address == 0x08) | (address == 0x09)) {
            return address + 0x80;
        } else {
            return address;
        }
    }

    //TMR0preWritten
    /**
     * Methode prueft ob der TMR0 vorbelegt wurde.
     * Pruefung anhand des Attributs TMR0preWritten.
     * Das Attribut wird bei den set Methoden gesetzt, wenn auf Bank1 geschrieben wird.
     * @return gibt true zuruek wenn vorbelegt wurde
     */
    public boolean isTMR0preWritten() {
        if (TMR0preWritten == true) {
            TMR0preWritten = false;
            return true;
        } else {
            return false;
        }
    }

    //Latch
    /**
     * Latchmethode fuer Byte-Werte
     * Schreibt den Wert der an PortA (PortB) gelegt werden soll in latchA (latchB).
     * @param address Adresse 05 oder 06
     * @param value (Byte) Wert der geschrieben werden soll
     */
    public void latch(int address, Byte value) {
        //Wert wird in Latch geschrieben
        if (address == 0x05) {
            latchA = value;
        }

        if (address == 0x06) {
            latchB = value;
        }
        // latchWrite wird aufgerufen um evtl. direkt den Wert auszugeben
        latchWrite();

    }

    /**
     * Latchmethode fuer Int-Werte.
     * Schreibt den Wert der an PortA (PortB) gelegt werden soll in latchA (latchB).
     * @param address Adresse 05 oder 06
     * @param value (Int) Wert der geschrieben werden soll
     */
    public void latchInt(int address, int value) {
        if (address == 0x05) {
            getLatchA().setByte(value);
        }

        if (address == 0x06) {
            getLatchB().setByte(value);
        }

        latchWrite();

    }

    /**
     * Latchmethode fuer Bits.
     * Schreibt den Wert der an PortA (PortB) gelegt werden soll in latchA (latchB).
     * @param address Adresse 05 oder 06
     * @param pos Position an die geschrieben werden soll
     * @param value (boolean) Wert der geschrieben werden soll
     */
    public void latchBit(int address, int pos, boolean value) {
        if (address == 5) {
            getLatchA().setBit(pos, value);
        }
        if (address == 6) {
            getLatchB().setBit(pos, value);
        }

        if (register[0x85].getBit(pos) == false) {
            register[0x05].setBit(pos, getLatchA().getBit(pos));
        }
        if (register[0x86].getBit(pos) == false) {
            register[0x06].setBit(pos, getLatchB().getBit(pos));
        }
    }

    /**
     * Methode zum Ausgeben des Latch an PortA (PortB).
     * Es wird im TrisA (TrisB) uerberprueft, ob betreffender Pin Ausgang ist.
     * (Tris-Register an Pin-Pos = 0)
     */
    public void latchWrite() {

        //prüfen der PortA Ausgänge. Wenn Ausgang, dann auf PortA schreiben
        if (register[0x85].getBit(0) == false) {
            register[5].setBit(0, getLatchA().getBit(0));
        }
        if (register[0x85].getBit(1) == false) {
            register[5].setBit(1, getLatchA().getBit(1));
        }
        if (register[0x85].getBit(2) == false) {
            register[5].setBit(2, getLatchA().getBit(2));
        }
        if (register[0x85].getBit(3) == false) {
            register[5].setBit(3, getLatchA().getBit(3));
        }
        if (register[0x85].getBit(4) == false) {
            register[5].setBit(4, getLatchA().getBit(4));
        }

        //prüfen der PortB Ausgänge. Wenn Ausgang, dann auf PortB schreiben
        if (register[0x86].getBit(0) == false) {
            register[6].setBit(0, getLatchB().getBit(0));
        }
        if (register[0x86].getBit(1) == false) {
            register[6].setBit(1, getLatchB().getBit(1));
        }
        if (register[0x86].getBit(2) == false) {
            register[6].setBit(2, getLatchB().getBit(2));
        }
        if (register[0x86].getBit(3) == false) {
            register[6].setBit(3, getLatchB().getBit(3));
        }
        if (register[0x86].getBit(4) == false) {
            register[6].setBit(4, getLatchB().getBit(4));
        }
        if (register[0x86].getBit(5) == false) {
            register[6].setBit(5, getLatchB().getBit(5));
        }
        if (register[0x86].getBit(6) == false) {
            register[6].setBit(6, getLatchB().getBit(6));
        }
        if (register[0x86].getBit(7) == false) {
            register[6].setBit(7, getLatchB().getBit(7));
        }

    }

    /**
     * @return the latchA
     */
    public Byte getLatchA() {
        return latchA;
    }

    /**
     * @return the latchB
     */
    public Byte getLatchB() {
        return latchB;
    }
}


